/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package edu.pdx.cs.guavadata;

import edu.pdx.cs.guavadata.condition.Condition;
import edu.pdx.cs.guavadata.condition.ConditionException;
import edu.pdx.cs.guavadata.domain.Domain;
import edu.pdx.cs.guavadata.query.AntiSemiJoin;
import edu.pdx.cs.guavadata.query.ApplyOp;
import edu.pdx.cs.guavadata.query.Command;
import edu.pdx.cs.guavadata.query.CrossJoin;
import edu.pdx.cs.guavadata.query.Except;
import edu.pdx.cs.guavadata.query.Filter;
import edu.pdx.cs.guavadata.query.Insert;
import edu.pdx.cs.guavadata.query.Intersect;
import edu.pdx.cs.guavadata.query.Join;
import edu.pdx.cs.guavadata.query.Operator;
import edu.pdx.cs.guavadata.query.PivotOp;
import edu.pdx.cs.guavadata.query.Project;
import edu.pdx.cs.guavadata.query.SqlProvider;
import edu.pdx.cs.guavadata.query.TableRef;
import edu.pdx.cs.guavadata.query.Union;
import edu.pdx.cs.guavadata.query.UnpivotOp;
import edu.pdx.cs.guavadata.query.Update;
import edu.pdx.cs.guavadata.subclass.Cell;
import edu.pdx.cs.guavadata.subclass.ConditionType;
import edu.pdx.cs.guavadata.subclass.DataColumn;
import edu.pdx.cs.guavadata.subclass.DataSet;
import edu.pdx.cs.guavadata.subclass.DataTable;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 *
 * @author VuTran
 */
public class Channel {

    private Cell root;
    private SqlProvider sp;
    private Map<String, Operator> qo;
    private Map<String, Filter> q1;
    private Map<String, Integer> ca;

    public Channel() {
        sp = new SqlProvider();
        qo = new HashMap<String, Operator>();
        q1 = new HashMap<String, Filter>();
        ca = new HashMap<String, Integer>();
        root = sp;
    }

    public void add(Transform p) {
        try {
            if (p == null) {
                throw new Exception("Trying to add a null information");
            }
        } catch (Exception e) {
            System.err.println("ERROR: " + e.toString());
        }
        p.child = root;
        root = p;
        
    }

    public static Channel fromDocument(Document doc) {
        Channel c = new Channel();
        try {
            if (doc.getFirstChild().hasChildNodes() == false) {
                throw new Exception("No element");
            }
            if (doc.getFirstChild().getChildNodes().getLength() == 0) {
                throw new Exception("no element");
            }
        } catch (Exception e) {
            System.err.println("ERROR: " + e.toString());
        }
        NodeList childNode = doc.getFirstChild().getChildNodes();
        for (int i = childNode.getLength() - 1; i >=0; i--) {
            c.add(Transform.fromElement((Element) childNode.item(i)));
        }
        return c;
    }

    public static Channel invertChannel(Channel c) {
        Channel newC = new Channel();
        Transform t = (Transform) c.root;
        /*  -- not implemented yet
        while (!(t instanceof SqlProvider)) {
            newC.add(t.inverse());
            t = (Transform) t.child;
        }
         */
        return newC;
    }

    public ResultSet poseRawQuery(String s) {
        return DBConnection.executeQuery(s);
    }

    public ResultSet poseQueryCommand(Command cold) {
        Transaction t = new Transaction();

            /*replace select with pre-transformed tablerefs
            if (cold.root is Filter)
            {
                Filter f1 = (Filter)cold.root;
                TableRef tr = (TableRef)f1.Child;
                string tabName = tr.Table.ToString();
                /*if (qo.ContainsKey(tr.Table.ToString()))
                {
                    f.Child = qo[tr.Table.ToString()].Clone();
                    f.Child.exposedColumns = tr.exposedColumns;
                    Command com = new Command();
                    com.root = f;
                    t.Add(com);
                    return sp.Process(t);
                }
                if (q1.ContainsKey(tabName))
                {
                    Filter f2 = (Filter)q1[tabName].Clone();
                    List<string> newcolumns = new List<string>();
                    for (int i = 0; i < f1.ColumnAliases.Count; i++)
                    {
                        string col = f1.ColumnAliases[i].Split('.')[1];
                        int alias = ca[tabName + "." + col];
                        newcolumns.Add("T_" + alias + "." + col);
                    }
                    f2.ColumnAliases = newcolumns;
                    f2.ColumnConditions = f1.ColumnConditions;
                    Command com = new Command();
                    com.root = f2;
                    t.Add(com);
                    return sp.Process(t);
                }
            }*/

        t.add(cold);

        return root.process(t);
    }

    public ResultSet poseTransaction(Transaction t) {
        return root.process(t);
    }

    public Document toDocument() {
        Document doc = null;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            doc = db.newDocument();
            Element rootElement = doc.createElement("Channel");
            doc.appendChild(rootElement);

            // missing sth here
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(Channel.class.getName()).log(Level.SEVERE, null, ex);
        }

        return doc;
    }

    public void preseedQueries(DataSet ds) {
        for (DataTable table : ds.tables) {
            Command com = new Command();
            String tab = com.nextTableAlias(table.getName());
            String tabName = table.getName();
            TableRef tr = new TableRef(table.toString(), tab);
            for (DataColumn dc : AppData.ds.getTable(tabName).columns) {
                tr.addColumn(dc.getName(), dc.getName(), (Domain) dc.extendedProperties.get(Operator.domainFlag));
            }

            Filter f = null;
            try {
                List<String> lsString = new ArrayList<String>();
                lsString.add(tab + ".id");
                List<Condition> lsCond = new ArrayList<Condition>();
                lsCond.add(Condition.newCondition(ConditionType.EQ, "-__-"));
                f = new Filter(tr, lsString, lsCond);
                com.root = f;
            } catch (ConditionException ex) {
               System.err.println("ERROR: " + ex.toString());
            }

            this.poseQueryCommand(com);

            qo.put(tabName, f.getChild());
            this.mapAliases(f, tabName);
            q1.put(tabName, f);
        }
        return;
    }

    public void mapAliases(AntiSemiJoin asj, String table) {
        asj.getChild1().visit(this, table);
        asj.getChild2().visit(this, table);
        return;
    }

    public void mapAliases(ApplyOp ap, String table) {
        ap.getChild().visit(this, table);
        return;
    }

    public void mapAliases(CrossJoin cj, String table) {
        cj.getChild1().visit(this, table);
        cj.getChild2().visit(this, table);
        return;
    }

    public void mapAliases(Except e, String table) {
        e.getChild1().visit(this, table);
        e.getChild2().visit(this, table);
        return;
    }

    public void mapAliases(Filter f, String table) {
        f.getChild().visit(this, table);
        return;
    }

    public void mapAliases(Insert i, String table) {
        i.getChild().visit(this, table);
        return;
    }

    public void mapAliases(Intersect i, String table) {
        for (int j = 0; j < i.getChildren().size(); j++) {
            i.getChildren().get(j).visit(this, table);
        }
        return;
    }

    public void mapAliases(Join j, String table) {
        j.getChild1().visit(this, table);
        j.getChild2().visit(this, table);
        return;
    }

    public void mapAliases(Operator o, String table) {
        return;
    }

    public void mapAliases(PivotOp po, String table) {
        int alias = Integer.valueOf(po.getNewAlias().split("_")[1]);
        for (String col : po.exposedColumns) {
            if (!ca.containsKey(table + "." + col))
                ca.put(table + "." + col, alias);
        }
        return;
    }

    public void mapAliases(Project p, String table) {
        p.getChild().visit(this, table);
        return;
    }

    public void mapAliases(TableRef tr, String table) {
        int alias = Integer.valueOf(tr.getAlias().split("_")[1]);
        for (String col : tr.exposedColumns) {
            if (!ca.containsKey(table + "." + col))
                ca.put(table + "." + col, alias);
        }
        return;
    }

    public void mapAliases(Union u, String table) {
        for (int i = 0; i < u.getChildren().size(); i++) {
            u.getChildren().get(i).visit(this, table);
        }
        return;
    }

    public void mapAliases(UnpivotOp uo, String table) {
        uo.getChild().visit(this, table);
        return;
    }

    public void mapAliases(Update up, String table) {
        up.getChild().visit(this, table);
        return;
    }
}
